package org.testmyapp.tasks;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.activiti.engine.form.FormProperty;
import org.activiti.engine.task.Task;
import org.springframework.beans.BeansException;
import org.testmyapp.Overview;
import org.testmyapp.SessionCheckedHttpServlet;
import org.testmyapp.application.ApplicationContextProvider;
import org.testmyapp.engine.IEngineServices;
import org.testmyapp.entities.TaskWorkerBean;
import org.testmyapp.entities.TaskWorkerBeanPk;
import org.testmyapp.entities.TaskWorkerUIBean;
import org.testmyapp.entities.TaskWorkerUIBeanPk;
import org.testmyapp.persistence.BusinessObjectPersistenceProvider;
import org.testmyapp.persistence.ConfigObjectPersistenceProvider;
import org.testmyapp.print.LoginPrint;
import org.testmyapp.print.TaskServletPrint;
import org.testmyapp.session.SessionForUser;

public class TaskServlet extends SessionCheckedHttpServlet implements Servlet {
	
	// Incase of persistence error retry once (errors is allocation of
	// connection my appear.)
	private boolean retrySwitch;

	@Inject
	private IEngineServices engineServices;

	@Inject
	private SessionForUser sessionUser;

	@Inject
	private ApplicationContextProvider appContext;

	@Inject
	private ConfigObjectPersistenceProvider confObjPersProv;

	@Inject
	private BusinessObjectPersistenceProvider bopp;

	public enum TaskState {
		INITIAL("initial"), PROCESSINPUT("processinput"), COMPLETE("complete");
		TaskState(String state) {
			this.state = state;
		}

		public String getState() {
			return state;
		}

		String state;

		public static TaskState getTaskState(String state) {
			if (PROCESSINPUT.getState().equals(state)) {
				return PROCESSINPUT;
			}
			if (PROCESSINPUT.getState().equals(state)) {
				return PROCESSINPUT;
			}
			if (COMPLETE.getState().equals(state)) {
				return COMPLETE;
			}
			return null;
		}
	}

	public static String servletName = "TaskServlet";

	public static String stateAttribute = "state";
	public static String taskAttribute = "task";
	public static String seperator = ",";
	public static String varList = "varList";
	public static String rerouteError = "rerouteMessage";
	
	private String taskId = null;

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		// Mandatory for every servlet http method!!!!
		redirectIfSessionCheckFails(req, resp, sessionUser);
		
		logoutSessionCheckAndExecution(sessionUser, req, resp);

		String taskId = (String) req.getAttribute(TaskServlet.taskAttribute);
		if (taskId == null) {
			taskId = (String) req.getParameter(TaskServlet.taskAttribute);
		} else {
			this.taskId = taskId;
		}
		TaskState taskState = (TaskState) req.getAttribute(TaskServlet.stateAttribute);
		if (taskState == null) {
			taskState = TaskState.getTaskState(req.getParameter(TaskServlet.stateAttribute));
		}

		checkValidState(req, resp, this.taskId, taskState);

		Task taskById = engineServices.getTaskById(this.taskId);

		String processDefinitionId = taskById.getProcessDefinitionId();
		String processDefinitionKey = engineServices.getProcessDefinitionKeyForTaskDefKey(processDefinitionId);
		String taskDefinitionKey = taskById.getTaskDefinitionKey();

		TaskWorkerUIBean twBeanUI = confObjPersProv.find(TaskWorkerUIBean.class, new TaskWorkerUIBeanPk(processDefinitionKey,
				taskDefinitionKey));

		System.out.println("retrivieving ui bean: " + twBeanUI);

		TaskUI twUI = (twBeanUI != null) ? (TaskUI) appContext.getBean(twBeanUI.getBean()) : null;

		resp.setContentType(SessionCheckedHttpServlet.responseType);
		PrintWriter writer = resp.getWriter();

		String descr = taskById.getDescription();

		List<String> commonOutPut = TaskServletPrint.commonOutPut(resp, taskDefinitionKey, descr);
		print(commonOutPut, writer);
		print(LoginPrint.printLogoutButton(), writer);
		List<FormProperty> formPropertiesForTask = engineServices.getFormPropertiesForTask(taskById);
		switch (taskState) {
		case INITIAL:
			if (twUI == null) {
				formPropertiesForTask = orderListByWritable(formPropertiesForTask);
				List<String> initialOutPut = TaskServletPrint.initialOutPut(formPropertiesForTask, this.taskId);

				print(initialOutPut, writer);
				List<String> error = new ArrayList<String>();
				error.add((String) req.getAttribute(TaskServlet.rerouteError));
				print(error, writer);
			} else {
				String createUIElementsForUserTask = twUI.createUIElementsForUserTask(null, null,
						engineServices.getProcVariables(taskById.getProcessInstanceId()));
				writer.println(createUIElementsForUserTask);
			}

			break;
		case PROCESSINPUT:
			String validate = null;
			retrySwitch = true;
			boolean error = false;
			try {
				@SuppressWarnings("unchecked")
				Map<String, String[]> parameterMap = (Map<String, String[]>) req.getParameterMap();
				for (FormProperty fp : formPropertiesForTask) {
					if (parameterMap.get(fp.getId()) == null && fp.isRequired()) {
						throw new RuntimeException("Required field \"" + fp.getName() + "\" is left blank.");
					}
				}

				TaskWorkerBean twBean = confObjPersProv.find(TaskWorkerBean.class, new TaskWorkerBeanPk(processDefinitionKey,
						taskDefinitionKey));

				System.out.println("retrivieving bean: " + twBean.getBean());
				TaskWorker tw = (TaskWorker) appContext.getBean(twBean.getBean());

				validate = tw.validate(engineServices, parameterMap, taskById.getProcessInstanceId(), bopp);
			} catch (BeansException be) {
				System.out.println("No bean for task, error: " + be.getLocalizedMessage());
				be.printStackTrace(System.out);
			} catch (NullPointerException be) {
				System.out.println("No bean for task, error: " + be.getLocalizedMessage());
				be.printStackTrace(System.out);
			} catch (Exception e) {
				// reroute new Entry, still needs Message
				if (twUI == null) {
					req.setAttribute(TaskServlet.taskAttribute, taskById.getId());
					req.setAttribute(TaskServlet.rerouteError, e.getMessage() + " | " + e.getStackTrace()[0]);
					req.setAttribute(TaskServlet.stateAttribute, TaskServlet.TaskState.INITIAL);
					System.err.println("Forwarding @ same servlet, error ocurred");
					e.printStackTrace(writer);
					req.getRequestDispatcher(TaskServlet.servletName).forward(req, resp);	
				}
				else {
					error = true;
					writer.println(twUI.createUIElementsForUserTask(null, e.getMessage(), engineServices.getProcVariables(taskById.getProcessInstanceId())));
				}
			}
			// writer.println("<h1>" + TaskState.PROCESSINPUT.getState() +
			// "</h1>");
			if (twUI == null) {
				req.setAttribute(TaskServlet.taskAttribute, taskById.getId());
				req.setAttribute(TaskServlet.stateAttribute, TaskServlet.TaskState.COMPLETE);
				req.getRequestDispatcher(TaskServlet.servletName).forward(req, resp);
			} else {
				if (validate != null && validate.equals(GeneralUI.COMPLETE_TASK)) {
					req.setAttribute(TaskServlet.taskAttribute, taskById.getId());
					req.setAttribute(TaskServlet.stateAttribute, TaskServlet.TaskState.COMPLETE);
					req.getRequestDispatcher(TaskServlet.servletName).forward(req, resp);
				}
				else {
					// if error we already printed!
					if (!error) writer.println(twUI.createUIElementsForUserTask(validate, null, engineServices.getProcVariables(taskById.getProcessInstanceId())));
					
				}
			}
			break;
		case COMPLETE:
			List<String> completeUserTaskWithSucessorDetermination=null;
			try {
			completeUserTaskWithSucessorDetermination = engineServices.completeUserTaskWithSucessorDetermination(taskById);
			}
			// retry block for exception in task completion
			catch (Exception e) {
				if (retrySwitch) {
					completeUserTaskWithSucessorDetermination = engineServices.completeUserTaskWithSucessorDetermination(taskById);
					retrySwitch = false;
				}
			}
			if (completeUserTaskWithSucessorDetermination.size() == 1) {
				req.setAttribute(TaskServlet.taskAttribute, completeUserTaskWithSucessorDetermination.get(0));
				req.setAttribute(TaskServlet.stateAttribute, TaskServlet.TaskState.INITIAL);
				req.getRequestDispatcher(SessionCheckedHttpServlet.processServlet).forward(req, resp);
				break;
			}
			if (completeUserTaskWithSucessorDetermination.size() > 1) {
				req.setAttribute(Overview.priortisedTasks, completeUserTaskWithSucessorDetermination);
			}
			req.getRequestDispatcher(Overview.overviewServlet).forward(req, resp);
			break;
		}

	}
	
	public void checkValidState(HttpServletRequest req, HttpServletResponse resp, String taskId, TaskState taskState)
			throws ServletException, IOException {
		if (taskState == null || taskId == null) {
			req.getRequestDispatcher(SessionCheckedHttpServlet.overviewServlet).forward(req, resp);
		}
	}

	/**
	 * 
	 * @return List of non-writable with values, alters input to writeable only
	 */
	public List<FormProperty> orderListByWritable(List<FormProperty> input) {
		List<FormProperty> result = new ArrayList<FormProperty>();
		for (FormProperty fp : input) {
			if (!fp.isWritable()) {
				result.add(fp);
			}
		}
		input.removeAll(result);
		result.addAll(input);
		return result;
	}

}

